package com.cx.core.redis.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class GeoService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public static final String GEO_CACHE_KEY = "geo_cache_key";

    /**
     * 将指定的地理空间位置（纬度、经度、名称）添加到指定的key中。
     *
     * @param cacheKey 缓存key
     * @param point    经纬度位置
     * @param certId   成员标识
     * @return boolean
     * @author compass
     * @date 2023/2/12 23:39
     * @since 1.0.0
     **/
    public boolean geoAdd(String cacheKey, Point point, String certId) {
        Long result = redisTemplate.opsForGeo().add(cacheKey, point, certId);
        return result != null && result > 0;
    }


    /**
     * 将经纬度信息添加到redis中
     *
     * @param cacheKey 缓存主键
     * @param params   params key为成员标识，value为经纬度信息
     * @return boolean
     * @author compass
     * @date 2023/2/13 9:57
     * @since 1.0.0
     **/
    public boolean geoAddAll(String cacheKey, Map<Object, Point> params) {
        GeoOperations<String, Object> ops = redisTemplate.opsForGeo();
        Long result = ops.add(cacheKey, params);
        return result != null && result > 0;

    }

    /**
     * 计算两个人之间的距离
     *
     * @param cacheKey    缓存主键
     * @param selfCertId  第一个标识
     * @param otherCertId 第二个标识
     * @return double
     * @author compass
     * @date 2023/2/13 10:23
     * @since 1.0.0
     **/
    public double distanceBetween(String cacheKey, String selfCertId, String otherCertId) {
        GeoOperations<String, Object> geoOperations = redisTemplate.opsForGeo();
        Distance distance = geoOperations.distance(cacheKey, selfCertId, otherCertId);
        if (distance != null) {
            return distance.getValue();
        }
        return -1;
    }


    /**
     * 查询距离某个人指定范围内的人，包括距离多少米
     *
     * @param cacheKey 缓存主键
     * @param certId   目标标识
     * @param distance 距离（米）
     * @param limit    距离单位
     * @param limit    限制个数
     * @return java.util.Map<java.lang.String, java.lang.Double>
     * @author compass
     * @date 2023/2/13 9:59
     * @since 1.0.0
     **/
    public Map<String, Double> distanceInclude(String cacheKey, String certId, double distance, long limit, RedisGeoCommands.DistanceUnit distanceUnit) {
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .sortAscending()// 排序
                .limit(limit)// 输出元素的个数
                .includeCoordinates();// 输出经纬度
        GeoResults<RedisGeoCommands.GeoLocation<Object>> geoResults = redisTemplate.opsForGeo().radius(cacheKey, certId, new Distance(distance, distanceUnit), geoRadiusCommandArgs);
        HashMap<String, Double> resultMap = new HashMap<>();
        if (geoResults != null) {
            List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = geoResults.getContent();
            for (GeoResult<RedisGeoCommands.GeoLocation<Object>> item : content) {
                RedisGeoCommands.GeoLocation<Object> itemContent = item.getContent();
                String otherCertId = itemContent.getName().toString();
                double between = distanceBetween(cacheKey, certId, otherCertId);
                resultMap.put(otherCertId, between);
            }
        }
        return resultMap;
    }

    /**
     * 查询距离某个人指定范围内的人，包括距离多少米
     * @param cacheKey 缓存主键
     * @param certId   目标标识
     * @param distance 距离（米）
     * @return java.util.Map<java.lang.String, java.lang.Double>
     * @author compass
     * @date 2023/2/13 11:06
     * @since 1.0.0
     **/
    public Map<String, Double> distanceInclude(String cacheKey, String certId, double distance) {
        Map<String, Double> map = new LinkedHashMap<>();
        GeoOperations geoOperations = redisTemplate.opsForGeo();
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = geoOperations.radius(cacheKey, certId, new Distance(distance), geoRadiusCommandArgs.includeDistance());
        if (geoResults != null) {
            Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = geoResults.iterator();
            while (iterator.hasNext()) {
                GeoResult<RedisGeoCommands.GeoLocation<String>> geoResult = iterator.next();
                // 与目标点相距的距离信息
                Distance geoResultDistance = geoResult.getDistance();
                // 该点的信息
                RedisGeoCommands.GeoLocation<String> geoResultContent = geoResult.getContent();
                map.put(geoResultContent.getName(), geoResultDistance.getValue());
            }
        }
        return map;
    }


    /**
     * 获取指定目标的经纬度
     *
     * @param cacheKey 缓存主键
     * @param certId   目标标识
     * @return java.util.List<org.springframework.data.geo.Point>
     * @author compass
     * @date 2023/2/13 3:06
     * @since 1.0.0
     **/
    public List<Point> position(String cacheKey, String certId) {
        return redisTemplate.opsForGeo().position(cacheKey, certId);
    }

    /**
     * 将指定单位从geo中异常
     *
     * @param cacheKey 缓存主键
     * @param certIds  成员标识
     * @return boolean
     * @author compass
     * @date 2023/2/13 9:56
     * @since 1.0.0
     **/
    public boolean remove(String cacheKey, String... certIds) {
        Long result = redisTemplate.opsForGeo().remove(cacheKey, certIds);
        return result != null && result > 0;
    }

    /**
     * 获取地理位置的哈希值
     *
     * @param cacheKey 缓存主键
     * @param certIds  成员标识
     * @return java.util.List<java.lang.String>
     * @author compass
     * @date 2023/2/13 10:22
     * @since 1.0.0
     **/
    public List<String> hashByCertId(String cacheKey, String... certIds) {
        return redisTemplate.opsForGeo().hash(cacheKey, certIds);
    }
}
